/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <glib.h>
#include "misc.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "frame.h"
#include "screen.h"
#include "focus_cycle.h"
#include "focus_cycle_indicator.h"
#include "focus.h"
enum cycle_type {
	CYCLE_NONE = 0,
	CYCLE_NORMAL,
	CYCLE_DIRECTIONAL
};

struct wm_client *focus_cycle_target = NULL;
static enum cycle_type focus_cycle_type = CYCLE_NONE;
static gboolean focus_cycle_linear;
static gboolean focus_cycle_all_desktops;
static gboolean focus_cycle_nonhilite_windows;
static gboolean focus_cycle_desktop_windows;

void focus_cycle_startup(void)
{
}

void focus_cycle_shutdown(void)
{
}

void focus_cycle_addremove(struct wm_client *c, gboolean redraw)
{
	if(!focus_cycle_type)
		return;
	if(focus_cycle_type == CYCLE_DIRECTIONAL) {
		if(c && focus_cycle_target == c) {
			focus_directional_cycle(0, TRUE, TRUE, TRUE, TRUE, TRUE);
		}
	} else if(redraw) {
		focus_cycle_reorder();
	}
}

void focus_cycle_reorder()
{
	if(focus_cycle_type == CYCLE_NORMAL) {
		focus_cycle_update_indicator(focus_cycle_target);
		if(!focus_cycle_target)
			focus_cycle(TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
	}
}

struct wm_client *focus_cycle(gboolean forward, gboolean all_desktops, gboolean nonhilite_windows, gboolean desktop_windows,
	gboolean linear, gboolean interactive, gboolean showbar, gboolean done,
	gboolean cancel)
{
	static GList *order = NULL;
	GList *it, *start, *list;
	struct wm_client *ft = NULL;
	struct wm_client *ret = NULL;
	if(interactive) {
		if(cancel) {
			focus_cycle_target = NULL;
			goto done_cycle;
		} else if(done)
			goto done_cycle;
		if(!focus_order)
			goto done_cycle;
		if(linear)
			list = client_list;
		else
			list = focus_order;
	} else {
		if(!focus_order)
			goto done_cycle;
		list = client_list;
	}
	if(focus_cycle_target == NULL) {
		focus_cycle_linear = linear;
		focus_cycle_all_desktops = all_desktops;
		focus_cycle_nonhilite_windows = nonhilite_windows;
		focus_cycle_desktop_windows = desktop_windows;
		start = it = g_list_find(list, focus_client);
	} else
		start = it = g_list_find(list, focus_cycle_target);
	if(!start)
		start = it = forward ? g_list_last(list) : g_list_first(list);
	if(!start)
		goto done_cycle;
	do {
		if(forward) {
			it = it->next;
			if(it == NULL)
				it = g_list_first(list);
		} else {
			it = it->prev;
			if(it == NULL)
				it = g_list_last(list);
		}
		ft = it->data;
		if(focus_cycle_valid(ft)) {
			if(interactive) {
				if(ft != focus_cycle_target) {
					focus_cycle_target = ft;
					focus_cycle_type = CYCLE_NORMAL;
					focus_cycle_draw_indicator(showbar ? ft : NULL);
				}
				return focus_cycle_target;
			} else if(ft != focus_cycle_target) {
				focus_cycle_target = ft;
				focus_cycle_type = CYCLE_NORMAL;
				done = TRUE;
				break;
			}
		}
	} while(it != start);
  done_cycle:if(done && !cancel)
		ret = focus_cycle_target;
	focus_cycle_target = NULL;
	focus_cycle_type = CYCLE_NONE;
	g_list_free(order);
	order = NULL;
	if(interactive) {
		focus_cycle_draw_indicator(NULL);
	}
	return ret;
}

static struct wm_client *focus_find_directional(struct wm_client *c, enum wm_direction dir,
	gboolean desktop_windows)
{
	gint my_cx, my_cy, his_cx, his_cy;
	gint offset = 0;
	gint distance = 0;
	gint score, best_score;
	struct wm_client *best_client, *cur;
	GList *it;
	(void)desktop_windows;
	if(!client_list)
		return NULL;
	my_cx = c->frame->area.x + c->frame->area.width / 2;
	my_cy = c->frame->area.y + c->frame->area.height / 2;
	best_score = -1;
	best_client = c;
	for(it = g_list_first(client_list); it; it = g_list_next(it)) {
		cur = it->data;
		if(cur == c)
			continue;
		if(!focus_cycle_valid(it->data))
			continue;
		his_cx = (cur->frame->area.x - my_cx) + cur->frame->area.width / 2;
		his_cy = (cur->frame->area.y - my_cy) + cur->frame->area.height / 2;
		if(dir == WM_DIRECTION_NORTHEAST || dir == WM_DIRECTION_SOUTHEAST || dir == WM_DIRECTION_SOUTHWEST
			|| dir == WM_DIRECTION_NORTHWEST) {
			gint tx;
			tx = his_cx + his_cy;
			his_cy = -his_cx + his_cy;
			his_cx = tx;
		}
		switch (dir) {
		case WM_DIRECTION_NORTH:
		case WM_DIRECTION_SOUTH:
		case WM_DIRECTION_NORTHEAST:
		case WM_DIRECTION_SOUTHWEST:
			offset = (his_cx < 0) ? -his_cx : his_cx;
			distance = ((dir == WM_DIRECTION_NORTH || dir == WM_DIRECTION_NORTHEAST) ? -his_cy : his_cy);
			break;
		case WM_DIRECTION_EAST:
		case WM_DIRECTION_WEST:
		case WM_DIRECTION_SOUTHEAST:
		case WM_DIRECTION_NORTHWEST:
			offset = (his_cy < 0) ? -his_cy : his_cy;
			distance = ((dir == WM_DIRECTION_WEST || dir == WM_DIRECTION_NORTHWEST) ? -his_cx : his_cx);
			break;
		}
		if(distance <= 0)
			continue;
		score = distance + offset;
		if(offset > distance)
			score += 1000000;
		if(best_score == -1 || score < best_score) {
			best_client = cur;
			best_score = score;
		}
	}
	return best_client;
}

struct wm_client *focus_directional_cycle(enum wm_direction dir, gboolean desktop_windows, gboolean interactive,
	gboolean showbar, gboolean done, gboolean cancel)
{
	static struct wm_client *first = NULL;
	struct wm_client *ft = NULL;
	struct wm_client *ret = NULL;
	if(cancel) {
		focus_cycle_target = NULL;
		goto done_cycle;
	} else if(done && interactive)
		goto done_cycle;
	if(!focus_order)
		goto done_cycle;
	if(focus_cycle_target == NULL) {
		focus_cycle_linear = FALSE;
		focus_cycle_all_desktops = FALSE;
		focus_cycle_nonhilite_windows = TRUE;
		focus_cycle_desktop_windows = desktop_windows;
	}
	if(!first)
		first = focus_client;
	if(focus_cycle_target)
		ft = focus_find_directional(focus_cycle_target, dir, desktop_windows);
	else if(first)
		ft = focus_find_directional(first, dir, desktop_windows);
	else {
		GList *it;
		for(it = focus_order; it; it = g_list_next(it))
			if(focus_cycle_valid(it->data)) {
				ft = it->data;
				break;
			}
	}
	if(ft && ft != focus_cycle_target) {
		focus_cycle_target = ft;
		focus_cycle_type = CYCLE_DIRECTIONAL;
		if(!interactive)
			goto done_cycle;
		focus_cycle_draw_indicator(showbar ? ft : NULL);
	}
	return focus_cycle_target;
  done_cycle:if(done && !cancel)
		ret = focus_cycle_target;
	first = NULL;
	focus_cycle_target = NULL;
	focus_cycle_type = CYCLE_NONE;
	focus_cycle_draw_indicator(NULL);
	return ret;
}

gboolean focus_cycle_valid(struct wm_client *client)
{
	return focus_valid_target(client, screen_desktop, TRUE, focus_cycle_all_desktops,
		focus_cycle_nonhilite_windows, focus_cycle_desktop_windows, FALSE);
}
